/* base64.c : dgnsc base64 enc/dec
 * Copyright (C) 2009-2011 drangon <drangon.zhou@gmail.com>
 * 2011-12
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dgnsc/base64.h>

static const char s_base64_table[64] = { 
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 
	'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 
	'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
	'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
	'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
	'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', 
	'8', '9', '+', '/'
};

static const char s_base64_pad = '=';

static const signed char s_base64_reverse_table[128] = {
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
	-1, -1,	-1, -1, -1, -1, -1, -1, -1, -1, 
	-1, -1, -1, 62, -1, -1, -1, 63,	52, 53,
	54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
	-1, -1, -1, -1, -1,  0,  1,  2,  3,  4,
	 5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
	15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
	25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 
	29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 
	39, 40,	41, 42, 43, 44, 45, 46, 47, 48,
	49, 50, 51, -1, -1, -1, -1, -1
};

int dgn_base64_enc( char * dst, const char * src, int slen, int linelen )
{
	const unsigned char * s = (const unsigned char *)src;
	char * d = dst;
	int i, j, n;
	for( i = 0, j = 0, n = 0; i < slen - 2; i += 3, s += 3, j += 4, d += 4 ) {
		*d = s_base64_table[ *s >> 2 ];
		*(d + 1) = s_base64_table[ ((*s & 0x03) << 4) + (*(s+1) >> 4) ];
		*(d + 2) = s_base64_table[ ((*(s+1) & 0x0f) << 2) + (*(s+2) >> 6) ];
		*(d + 3) = s_base64_table[ *(s+2) & 0x3f ];
		n += 4;
		if( linelen > 0 && n >= linelen ) {
			n = 0;
			*(d + 4) = '\r';
			*(d + 5) = '\n';
			d += 2;
			j += 2;
		}
	}

	if( i == slen - 1 ) {
		*d = s_base64_table[ *s >> 2 ];
		*(d + 1) = s_base64_table[ ((*s & 0x03) << 4) ];
		*(d + 2) = s_base64_pad;
		*(d + 3) = s_base64_pad;
		j += 4;
		d += 4;
		n += 4;
	}
	else if( i == slen - 2 ) {
		*d = s_base64_table[ *s >> 2 ];
		*(d + 1) = s_base64_table[ ((*s & 0x03) << 4) + (*(s+1) >> 4) ];
		*(d + 2) = s_base64_table[ ((*(s+1) & 0x0f) << 2) ];
		*(d + 3) = s_base64_pad;
		j += 4;
		d += 4;
		n += 4;
	}

	if( linelen > 0 && n > 0 ) {
		*d = '\r';
		*(d + 1) = '\n';
		j += 2;
		d += 2;
	}
	*d = '\0';

	return j;
}

int dgn_base64_dec( char * dst, const char * src, int slen )
{
	int n = 0;
	signed char c[4], cc;
	int i = 0;
	int j = 0;
	for( i = 0; i < slen; ++i ) {
		if( src[i] == '\r' || src[i] == '\n' || src[i] == '\t' || src[i] == ' ' )
			continue;
		if( src[i] == '=' )
			break;
		if( src[i] < 0 || (cc = s_base64_reverse_table[ (int)src[i] ]) < 0 )
			return -1;
		c[n++] = cc;
		if( n == 4 ) {
			dst[j] = (c[0] << 2) | (c[1] >> 4);
			dst[j+1] = ((c[1] & 0x0f) << 4) | (c[2] >> 2);
			dst[j+2] = ((c[2] & 0x03) << 6) | c[3];
			n = 0;
			j += 3;
		}
	}

	if( n == 2 && (c[1] & 0x0f) == 0 ) {
		dst[j] = (c[0] << 2) | (c[1] >> 4);
		j += 1;
	}
	else if( n == 3 && (c[2] & 0x03) == 0 ) {
		dst[j] = (c[0] << 2) | (c[1] >> 4);
		dst[j+1] = ((c[1] & 0x0f) << 4) | (c[2] >> 2);
		j += 2;
	}
	dst[j] = '\0';

	return j;
}

static const char s_base91_table[91] = {
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
	'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
	'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
	'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$',
	'%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=',
	'>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"'
};     

static const char s_base91_reverse_table[128] = {
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, 62, 90, 63, 64, 65, 66, -1, 67, 68, 69, 70, 71, -1, 72, 73,
	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 74, 75, 76, 77, 78, 79,
	80,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 81, -1, 82, 83, 84,
	85, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 86, 87, 88, 89, -1,
};

/*
int gen_base91_reverse_table()
{
	char tmp[128];
	int i;
	for( i = 0; i < 128; i++ )
		tmp[i] = -1;
	for( i = 0; i < 91; i++ )
		tmp[ (unsigned char)s_base91_table[i] ] = i;
	printf( "const char s_base91_reverse_table[128] = {\n" );
	for( i = 0; i < 128; i++ ) {
		if( i % 16 == 0 )
			printf( "\t%d, ", tmp[i] );
		else if( i % 16 == 15 )
			printf( "%d,\n", tmp[i] );
		else
			printf( "%d, ", tmp[i] );
	}
	printf( "};\n" );

	return 0;
};
*/

int dgn_base91_enc( char * dst, const char * src, int slen )
{
	unsigned long val = 0;
	unsigned long tmp = 0;

	int bn = 0; // bitnum;
	int i, j;
	for( i = 0, j = 0, bn = 0, val = 0; i < slen; i++ ) {
		val |= src[i] << bn;
		bn += 8;
		if( bn > 13 ) {
			tmp = val & 8191;
			if( tmp > 88 ) {
				val >>= 13;
				bn -= 13;
			}
			else {
				tmp = val & 16383;
				val >>= 14;
				bn -= 14;
			}
			dst[j++] = s_base91_table[ tmp % 91 ];
			dst[j++] = s_base91_table[ tmp / 91 ];
		}
	}

	if( bn > 0 ) {
		dst[j++] = s_base91_table[ val % 91 ];
		if( bn > 7 || val > 90 )
			dst[j++] = s_base91_table[ val / 91 ];
	}

	dst[j] = '\0';

	return j;
}

int dgn_base91_dec( char * dst, const char * src, int slen )
{
	signed char * s = (signed char *)src;
	unsigned long val = 0;
	int dv = -1; // some flag
	int bn = 0; // bitnum;
	int i, j;
	for( i = 0, j = 0, bn = 0, val = 0; i < slen; i++ ) {
		if( s[i] < 0 || s_base91_reverse_table[ (int)s[i] ] < 0 )
			return -1;
		if( dv < 0 ) {
			dv = s_base91_reverse_table[ (int)s[i] ];
			continue;
		}

		dv += s_base91_reverse_table[ (int)s[i] ] * 91;
		val |= dv << bn;
		bn += ( dv & 8191 ) > 88 ? 13 : 14;
		do {
			dst[j++] = (char)( val & 0xFF );
			val >>= 8;
			bn -= 8;
		} while( bn > 7 );
		dv = -1;
	}
	
	if( dv != -1 ) {
		dst[j++] = (char)( val | dv << bn );
	}
		
	dst[j] = '\0';

	return j;
}

int dgn_base16_enc( char * dst, const char * src, int slen )
{
	int i;
	const unsigned char * s = (const unsigned char *)src;
	for( i = 0; i < slen; ++i ) {
		int c = (s[i] >> 4);
		dst[i * 2] = c < 10 ? '0' + c : 'A' + c - 10;
		c = (s[i] & 0x0f);
		dst[i * 2 + 1] = c < 10 ? '0' + c : 'A' + c - 10;
	}
	dst[i * 2] = '\0';
	return i * 2;
}

int dgn_base16_dec( char * dst, const char * src, int slen )
{
	int i = 0, j = 0, n = 0;
	unsigned char * d = (unsigned char *)dst;
	for( i = 0; i < slen; ++i ) {
		int c = 0;
		if( src[i] == '\r' || src[i] == '\n' || src[i] == '\t' || src[i] == ' ' )
			continue;
		if( src[i] >= '0' && src[i] <= '9' )
			c = src[i] - '0';
		else if( src[i] >= 'a' && src[i] <= 'f' )
			c = src[i] - 'a' + 10;
		else if( src[i] >= 'A' && src[i] <= 'F' )
			c = src[i] - 'A' + 10;
		else
			return -1;
		if( n == 0 ) {
			d[j] = c;
			n = 1;
		}
		else {
			d[j] = (d[j] << 4) + c;
			n = 0;
			j += 1;
		}
	}
	d[j] = '\0';
	return j;
}

